/*
 * Created by SharpDevelop.
 * User: mrose
 * Date: 6/6/2007
 * Time: 10:06 AM
 * 
 * To change this template use Tools | Options | Coding | Edit Standard Headers.
 */

using System;
using System.Diagnostics;
using System.Windows.Forms;
using EnvDTE;
using System.Collections;

namespace Webbness.VsTool
{
    /// <summary>
    /// Description of BuildIncrementer.
    /// </summary>
    public class BuildIncrementer
    {
    	public class IncrementingAssemblyVersion : IComparable
    	{
    		public int Major;
			public int Minor;
			public int Build;
			public int Revision;
			private static string[] defaultVersion = new string[4]{"0", "1", "0", "0"};
			
			public IncrementingAssemblyVersion(string value)
			{
				string[] strTemp = value.Split('.');
				for (int i = 0; i < strTemp.Length; i++)
				{
					defaultVersion[i] = strTemp[i];
				}
				Major = SetValue(defaultVersion[0]);
				Minor = SetValue(defaultVersion[1]);
				Build = SetValue(defaultVersion[2]);
				Revision = SetValue(defaultVersion[3]);
			}
			
			public override string ToString()
			{
				return Major + "." + Minor + "." + Build + "." + Revision;
			}
    		
			public void SetMajor(string value)
			{
				Major = SetValue(value);
			}
			
			public void SetMinor(string value)
			{
				Minor = SetValue(value);
			}
			
			public void SetBuild(string value)
			{
				Build = SetValue(value);
			}
			
			public void SetRevision(string value)
			{
				Revision = SetValue(value);
			}
			
			public void Increment()
			{
				Revision++;
				if (Revision >= 9999)
				{
					Revision = 0;
					Build++;
				}
				if (Build >= 9999)
				{
					Build = 0;
					Minor++;
				}
				if (Minor >= 9999)
				{
					Minor = 0;
					Major++;
				}
			}
			
			public int CompareTo(object obj)
			{
				IncrementingAssemblyVersion other = obj as IncrementingAssemblyVersion;
				if (other == null)
				{
					return String.Compare(this.ToString(), obj.ToString());
				}
				
				if (Major > other.Major)
					return -1;
				if (Major < other.Major)
					return 1;
				if (Minor > other.Minor)
					return -1;
				if (Minor < other.Minor)
					return 1;
				if (Build > other.Build)
					return -1;
				if (Build < other.Build)
					return 1;
				if (Revision > other.Revision)
					return -1;
				if (Revision < other.Revision)
					return 1;
				return 0;
			}
			
			private int SetValue(String value)
			{
				try
				{
					return Convert.ToInt32(value);
				}
				catch
				{
					return 0;
				}
			}
    	}
    	
        private DTE _dte;
		private string revisionNo;
		//private string[] defaultVersion;
		
		public string RevisionNumber 
		{
			get 
			{
				return revisionNo;
			}
		}
		
        public BuildIncrementer(DTE dte)
        {
            _dte = dte;
            //revisionNo = GenerateRevisionNumber();
            //defaultVersion = new string[4]{"1", "1", "0", "*"};
        }

        //' This event will be triggered after every build of a project
        //' You can modify the code below to only update projects that are active
        //' It currently will scan all projects in the solution for AssemblyInfo.cs files
        //' to update.
        public void IncrementBuildNumber_OLD()
        {
        	AssemblyVersion version = new AssemblyVersion();
        	
            if (_dte.Solution.SolutionBuild.LastBuildInfo != 0)
            {
                return;
            }
            int skippedProjects = 0;
            
            foreach (Project project in _dte.Solution.Projects)
            {
                ProjectItem assemblyInfoItem = GetAssemblyInfoItem(project);
                Boolean skipProject = false;
                if (assemblyInfoItem == null)
                {
                    skipProject = true;
                }

                if (skipProject)
                {
                    skippedProjects = skippedProjects + 1;
                }
                else
                {
                	string fileName = assemblyInfoItem.get_FileNames(1);
                	bool isUnderSCC = project.DTE.SourceControl.IsItemUnderSCC(fileName);
                    try
                    {
                        assemblyInfoItem.Open("{7651A701-06E5-11D1-8EBD-00A0C90F26EA}").Activate();
                        //'vsViewKindCode
                        TextDocument txtDoc = (TextDocument) assemblyInfoItem.Document.Object("TextDocument");
                        TextSelection foundText = GetBuildAndRevision(txtDoc, version);
                        if (foundText != null)
                        {
                        	if (isUnderSCC)
                        		CheckoutActiveItem();
                        	
                        	IncrementIfNotEmptyOrStar(version);
                        	SetToTimeOfDayIfNotEmptyOrStar(version, revisionNo);
                        	
                        	foundText.Text = version.ToString();
                        	
                        	assemblyInfoItem.Save(fileName);
                        	if (isUnderSCC)
                        		CheckinActiveItem();
                        	_dte.ActiveWindow.Close(vsSaveChanges.vsSaveChangesYes);
                        }
                    }
                    catch (Exception ex)
                    {
                        MessageBox.Show(ex.Message);
                    }
                }
            }

            Debug.WriteLine("Skipped " + skippedProjects + "projects");
        }

        //' This event will be triggered after every build of a project
        //' You can modify the code below to only update projects that are active
        //' It currently will scan all projects in the solution for AssemblyInfo.cs files
        //' to update.
        public void IncrementBuildNumber()
        {
        	AssemblyVersion version = new AssemblyVersion();
        	
            if (_dte.Solution.SolutionBuild.LastBuildInfo != 0)
            {
                return;
            }
            
            ArrayList items = new ArrayList();
            
            foreach (Project project in _dte.Solution.Projects)
            {
            	try
            	{
            		Property assemblyVersion = project.Properties.Item("AssemblyVersion");
            		if (assemblyVersion != null)
            		{
            			items.Add(new IncrementingAssemblyVersion(assemblyVersion.Value.ToString()));
            		}
            		
            		Property assemblyFileVersion = project.Properties.Item("AssemblyFileVersion");
            		if (assemblyFileVersion != null)
            		{
            			items.Add(new IncrementingAssemblyVersion(assemblyFileVersion.Value.ToString()));
            		}
            	}
            	catch (Exception e)
            	{
            		Debug.WriteLine(e.Message);
            		//bury the error and keep going...
            	}
            }
            
            items.Sort();
            
            IncrementingAssemblyVersion maxVersion = items[0] as IncrementingAssemblyVersion;
            maxVersion.Increment();
            revisionNo = maxVersion.ToString();
            
            foreach (Project project in _dte.Solution.Projects)
            {
            	try
            	{
            		Property assemblyVersion = project.Properties.Item("AssemblyVersion");
            		Property assemblyFileVersion = project.Properties.Item("AssemblyFileVersion");
            		assemblyVersion.Value = revisionNo;
                    assemblyFileVersion.Value = revisionNo;
                    project.Save(null);
            	}
            	catch (Exception ex)
            	{
            		Debug.WriteLine(ex.Message);
            	}
            }
        }

        //'Returns the full path of the first file
        //'in the project with the name AssemblyInfo
        protected virtual ProjectItem GetAssemblyInfoItem(Project project)
        {
            foreach (ProjectItem projectItem in project.ProjectItems)
            {
                for (short i = 0; i < projectItem.FileCount; i++)
                {
                	if (IsMeaningfulProject(projectItem.Kind))
                	{
                		string filepath = projectItem.get_FileNames(i);

                		int indexOfLastSep = 1;
                		indexOfLastSep = filepath.LastIndexOf(@"\");
                		if (indexOfLastSep < 1)
                			indexOfLastSep = 1;

                		String fileName = filepath.Substring(indexOfLastSep);

                		if (fileName.IndexOf("AssemblyInfo") > 0)
                			return projectItem;
                	}
                }
            }

            return null;
        }
		
        
        protected bool IsMeaningfulProject(string projectKind)
        {
        	switch (projectKind)
        	{
        		case EnvDTE.Constants.vsProjectItemKindSolutionItems:
        			return false;
        	}
        	return true;
        }
        
        protected virtual TextSelection GetBuildAndRevision(TextDocument textDocument, AssemblyVersion version)
        {
        	string[] defaultVersion = new string[4]{"1", "1", "0", "*"};
			
            TextSelection objSelection = textDocument.Selection;
            
			//Get the text between AssemblyVersion(" and ")
			if (objSelection.FindText("AssemblyVersion(\"", (int)vsFindOptions.vsFindOptionsNone) == false)
				return null;
			
            int objStartPosition = objSelection.BottomPoint.DisplayColumn;
            
            if (objSelection.FindText("\")]", (int)vsFindOptions.vsFindOptionsNone) == false)
            	return null;
            
            int objEndPosition = objSelection.AnchorPoint.DisplayColumn;
           
            //Get back to after the AssemblyVersion" part
            objSelection.StartOfLine(EnvDTE.vsStartOfLineOptions.vsStartOfLineOptionsFirstColumn, false);
            objSelection.FindText("AssemblyVersion(\"", (int)vsFindOptions.vsFindOptionsNone);
            objSelection.Collapse();
            objSelection.MoveToDisplayColumn(0, objEndPosition, true);

			string[] strTemp = objSelection.Text.Split('.');
			
			for (int i = 0; i < strTemp.Length; i++)
			{
				defaultVersion[i] = strTemp[i];
			}
			
			version.Major = defaultVersion[0];
			version.Minor = defaultVersion[1];
			version.Build = defaultVersion[2];
			version.Revision = defaultVersion[3];
			
			return objSelection;
        }


        //'Generates a revision number for this build by using the curent time
        protected virtual string GenerateRevisionNumber()
        {
            DateTime now = DateTime.Now;
            String hrs = now.Hour.ToString();
            String mins = now.Minute.ToString();
            String secs = now.Second.ToString();

            return hrs + mins + secs;
        }

        //'Retrieves the text from the range and if it star or empty then
        //'returns otherwise increments it
        protected virtual void IncrementIfNotEmptyOrStar(AssemblyVersion version)
        {
        	if (version.Build.Equals("*"))
        		return;
        	try
        	{
        		int buildNo = Convert.ToInt32(version.Build);
        		buildNo++;
        		version.Build = buildNo.ToString();
        	}
            catch
            {
            	//bury the error and do nothing
            }
        }

        //'Retrieves the text from the range and if it star or empty then
        //'returns otherwise sets it to the time of day
        protected virtual void SetToTimeOfDayIfNotEmptyOrStar(AssemblyVersion version, String revisionNo)
        {
            if (version.Revision.Equals("*"))
        		return;
            version.Revision = revisionNo;
        }

        protected virtual void CheckoutActiveItem()
        {
            Command checkoutCmd = _dte.Commands.Item("File.CheckOutDynamicSilent", 0);
            //'Check if the item is already checked out or not
            if (!checkoutCmd.IsAvailable)
            {
                Debug.WriteLine("Checkout cmd not available");

                return;
            }

            object customin = null;
            object customout = null;
            try
            {
                _dte.Commands.Raise(checkoutCmd.Guid, checkoutCmd.ID, ref customin, ref customout);
            }
            catch (Exception ex)
            {
                Debug.WriteLine(ex);
            }
        }

        protected virtual void CheckinActiveItem()
        {
            Command checkinCmd = _dte.Commands.Item("File.CheckInDynamicSilent", 0);
            //'Check if the item is already checked out or not
            if (!checkinCmd.IsAvailable)
            {
                Debug.WriteLine("Checkin cmd not available, something is wrong");
                return;
            }
            object customin = null;
            object customout = null;
            _dte.Commands.Raise(checkinCmd.Guid, checkinCmd.ID, ref customin, ref customout);
        }
    }
}
